import { createServer } from "http";
import { Server } from "socket.io";
import { ackCallback } from "./utils.js";
import {
  EMIT_CLIENT_CONFIG_INFO,
  EMIT_CLIENT_IMAGE_INFO,
  EMIT_CONFIG,
  EMIT_INIT,
  EMIT_KEYBOARD,
  EMIT_MOUSE,
  EMIT_SYNC_CLIENT_IMAGE_INFO,
  SOCKET_TYPE_CLIENT,
  SOCKET_TYPE_WEB,
} from "./constrants.js";
import { logError, logInfo } from "./log.js";

const httpServer = createServer();
const io = new Server(httpServer, { cors: true });
export class ServerSocket {
  constructor(socket) {
    this.socket = socket;
    this.key = socket.id;
  }

  sendMsg = (eventName, data) => {
    logInfo('sendmsg', eventName);

    return new Promise((resolve, reject) => {
      if (!this.socket.connected) {
        reject("socket don't connected");
        return;
      }

      this.socket.timeout(1000).emit(eventName, data, (err, res) => {
        if (err) {
          reject(err);
          return;
        }

        resolve(res);
      });
    });
  };

  destroy() {
    logInfo(this.key, "destroy");
  }
}

export class ServerSocketScreenClient extends ServerSocket {
  screenWidth = 0;
  screenHeight = 0;
  image = "";
  mouseEnable = false;
  keyboardEnable = false;
  imageEnable = false;

  constructor(socket) {
    super(socket);
  }
}
export class ServerSocketWeb extends ServerSocket {
  constructor(socket) {
    super(socket);
  }
}

export class GroupClientManager {
  socketClients = [];
  socketWebs = [];
  sync = false;
  clientChanged = true;

  addClient(sc) {
    if (this.socketClients.some((item) => item.key === sc.key)) {
      return;
    }

    const socket = sc.socket;
    
    socket.on(EMIT_CLIENT_IMAGE_INFO, ackCallback((data) => this.onSocketEventImageInfo(sc, data)));
    socket.on(EMIT_CLIENT_CONFIG_INFO, ackCallback((data) => this.onSocketEventConfigInfo(sc, data)));

    this.socketClients.push(sc);

    logInfo("socketClients length", this.socketClients.length);
    this.startSync()
  }

  onSocketEventConfigInfo = async (socketClient, data) => {
    logInfo('on configinfo event', data)
    socketClient.mouseEnable = data.mouseEnable
    socketClient.keyboardEnable = data.keyboardEnable
    socketClient.imageEnable = data.imageEnable

    this.startSync()
  }

  onSocketEventImageInfo = async (socketClient, data) => {
    logInfo('on imageinfo event', data.image?.length) 

    socketClient.screenWidth = data.screenWidth;
    socketClient.screenHeight = data.screenHeight;
    socketClient.image = data.image;

    this.startSync()
  };

  addWeb(sw) {
    if (this.socketWebs.some((item) => item.key === sw.key)) {
      return;
    }

    const socket = sw.socket;

    socket.on(EMIT_CONFIG, ackCallback(this.onSocketEventConfig));
    socket.on(EMIT_MOUSE, ackCallback(this.onSocketEventMouse));
    socket.on(EMIT_KEYBOARD, ackCallback(this.onSocketEventKeyboard));

    this.socketWebs.push(sw);

    logInfo("socketWebs length", this.socketWebs.length);

    this.startSync()
  }

  onSocketEventKeyboard = async (data) => {
    logInfo('on keyboard event', data)
    const allClientsSend = this.socketClients.map((item) => {
      return item.sendMsg(EMIT_KEYBOARD, data);
    });

    return Promise.all(allClientsSend);
  };

  onSocketEventMouse = async (data) => {
    logInfo('on mouse event', data)
    const allClientsSend = this.socketClients.map((item) => {
      return item.sendMsg(EMIT_MOUSE, data);
    });

    return Promise.all(allClientsSend);
  };

  onSocketEventConfig = async (data) => {
    logInfo('on config event', data)

    const allClientsSend = this.socketClients.map((item) => {
      const info = data.find((d) => d.key === item.key);

      if (info) {
        return item.sendMsg(EMIT_CONFIG, info);
      }

      return Promise.resolve();
    });

    return Promise.all(allClientsSend);
  };

  removeClient(key) {
    const findIndex = this.socketClients.findIndex((item) => item.key === key);

    if (findIndex >= 0) {
      logInfo('remove client', key)
      this.socketClients[findIndex].destroy();
      this.socketClients.splice(findIndex, 1);
    }
  }

  removeWeb(key) {
    const findIndex = this.socketWebs.findIndex((item) => item.key === key);

    if (findIndex >= 0) {
      logInfo('remove web', key)
      this.socketWebs[findIndex].destroy();
      this.socketWebs.splice(findIndex, 1);
    }
  }

  startSync() {
    if (this.sync) {
      return;
    }
    
    const webLen = this.socketWebs.length;

    if (webLen === 0) {
      this.sync = false;
      return;
    }

    this.clientChanged = true
    this.sync = true;
    this.loopEmitClientInfo();
  }

  loopEmitClientInfo() {
    if (!this.clientChanged) {
      this.sync = false
      logInfo('stop loopEmitClientInfo')
      return
    }

    const clientScreenInfo = this.socketClients.map((item) => {
      return {
        key: item.key,
        screenWidth: item.screenWidth,
        screenHeight: item.screenHeight,
        mouseEnable: item.mouseEnable,
        keyboardEnable: item.keyboardEnable,
        imageEnable: item.imageEnable,
        image: item.image,
      };
    });

    Promise.all(
      this.socketWebs.map((socketInfo) => {
        return socketInfo.sendMsg(
          EMIT_SYNC_CLIENT_IMAGE_INFO,
          clientScreenInfo
        );
      })
    )
      .then(() => {
        this.clientChanged = false
        this.loopEmitClientInfo();
      })
      .catch((err) => {
        logError("loopEmitClientInfo error");
        logError(err);
        this.sync = false;
      });
  }
}

const gcm = new GroupClientManager();

io.on("connection", (socket) => {
  const clientKey = socket.id;
  let clientType = "";

  logInfo(clientKey, "connection");

  async function onInit(data) {
    clientType = data.type;
    logInfo(clientKey, "EMIT_INIT", data.type, clientType);

    if (clientType === SOCKET_TYPE_CLIENT) {
      logInfo("add SOCKET_TYPE_CLIENT", socket.id);
      gcm.addClient(new ServerSocketScreenClient(socket));
    } else if (clientType === SOCKET_TYPE_WEB) {
      logInfo("add SOCKET_TYPE_WEB", socket.id);
      gcm.addWeb(new ServerSocketWeb(socket));
    }

    gcm.startSync();
  }

  function onClose() {
    logInfo(clientKey, "disconnect", "clientType: ", clientType);

    if (clientType === SOCKET_TYPE_CLIENT) {
      gcm.removeClient(clientKey);
    } else if (clientType === SOCKET_TYPE_WEB) {
      gcm.removeWeb(clientKey);
    }
  }

  socket.on(EMIT_INIT, ackCallback(onInit));
  socket.on("close", onClose);
  socket.on("disconnect", onClose);
});

httpServer.listen(4000);
